// Copyright (C) Kumo inc. and its affiliates.
// Author: Jeff.li lijippy@163.com
// All rights reserved.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
//

#pragma once

#include <string_view>
#include <cantor/utility/arena_allocator.h>
#include <turbo/log/logging.h>

namespace cantor {
    /// simple vector allocate by arena
    /// the memory are managed by caller.
    template<typename T>
    class Vector {
    public:
        Vector() {
        }

        /// no dtor for T,
        /// guard that T is allocated
        /// by arena
        ~Vector() {
        }

        constexpr size_t size() const {
            return _begin == nullptr ? 0 : _end - _begin;
        }

        constexpr size_t capacity() const {
            return _begin == nullptr ? 0 : _mem_end - _begin;
        }

        constexpr size_t remain() const {
            return _mem_end == nullptr ? 0 : _mem_end - _end;
        }

        void reserve(int32_t size, turbo::Arena &arena) {
            if (size <= capacity()) {
                return;
            }
            int32_t old_size = Vector<T>::size();
            T *new_mem = static_cast<T *>(arena.allocate(size * sizeof(T)));
            if (old_size > 0) {
                memcpy(new_mem, _begin, sizeof(T) * (_end - _begin));
            }
            // arena need not delete old vector
            _begin = new_mem;
            _end = _begin + old_size;
            _mem_end = _begin + size;
        }

        Vector &push_back(const Vector &list, turbo::Arena &arena) {
            reserve(list.size() + size(), arena);
            for (auto &item : list) {
                push_back(item, arena);
            }
            return *this;
        }

        Vector &push_back(const T &value, turbo::Arena &arena) {
            if (remain() > 0) {
                *_end = value;
                ++_end;
            } else {
                int32_t new_capacity = (capacity() + 1) << 1;
                reserve(new_capacity, arena);
                *_end = value;
                ++_end;
            }
            return *this;
        }

        const T &operator[](const int32_t index) const {
            DKCHECK(index < size()) << "over flow";
            return *(_begin + index);
        }

        T &operator[](const int32_t index) {
            DKCHECK(index < size()) << "over flow";
            return *(_begin + index);
        }

        const T &at(const int32_t index) const {
            DKCHECK(index < size()) << "over flow";
            return *(_begin + index);
        }

        T &at(const int32_t index) {
            DKCHECK(index < size()) << "over flow";
            return *(_begin + index);
        }

        const T *begin() const {
            return _begin;
        }

        const T *end() const {
            return _end;
        }

        const T *data() const {
            return _begin;
        }

        T *begin() {
            return _begin;
        }

        T *end() {
            return _end;
        }

        T *data() {
            return _begin;
        }

       constexpr bool empty() const {
            return _begin == nullptr ? true : _end == _begin ? true : false;
        }

        constexpr void pop_back(size_t n = 1) {
            if (_begin == nullptr) {
                return;
            }
            if (_end - _begin >= n) {
                _end -= n;
            } else {
                _end = _begin;
            }
        }

       constexpr  void pop_front(size_t n = 1) {
            if (_begin == nullptr) {
               return;
            }
            if (_end - _begin >= n) {
                _begin += n;
            } else {
                _begin = _end;
            }
        }

        void clear() {
            _end = _begin;
        }

        const T &front() const{
            DKCHECK(size() > 0);
            return *_begin;
        }

        T &front() {
            DKCHECK(size() > 0);
            return *_begin;
        }

        const T &back() const {
            DKCHECK(size() > 0);
            return *(_end - 1);
        }

        T& back() {
            DKCHECK(size() > 0);
            return *(_end - 1);
        }

    private:
        T *_begin = nullptr;
        T *_end = nullptr;
        T *_mem_end = nullptr;
    };

    template<typename T>
    Vector<T>* make_vector(std::initializer_list<T> list, turbo::Arena &arena) {
        auto vec = alloc_type<Vector<T>>(arena);
        for (auto &item : list) {
            vec->push_back(item, arena);
        }
        return vec;
    }

    template<typename T>
   Vector<T>* make_vector(T t, turbo::Arena &arena) {
        auto vec = alloc_type<Vector<T>>(arena);
        vec->push_back(t, arena);
        return vec;
    }

} // namespace cantor
